#include "Pch.h"
#include "glUtil.h"
#include "Config.h"

mglMesh *mglMakeSphereMesh(double radius, int res) {
	int numVerts = res*res*6;
	mglNormalVertex *verts = (mglNormalVertex*)malloc(numVerts * (sizeof(mglNormalVertex) + sizeof(int) + 4 * sizeof(GLushort)));
	int *vertIndices = (int*) (verts + numVerts);
	GLushort *indices = (GLushort*) (vertIndices + numVerts);
	float rf = (float)radius;
	for (int x = 0; x<res; x++) {
		for (int y = 0; y<res; y++) {
			float xf = (x/(float)(res-1))*2-1;
			float yf = (y/(float)(res-1))*2-1;
			float scale = (float)(1/sqrt(xf*xf + yf*yf + 1));
			xf *= scale;
			yf *= scale;

			verts[x + y*res].n3f[0] = xf;
			verts[x + y*res].n3f[1] = yf;
			verts[x + y*res].n3f[2] = scale;

			verts[x + y*res + res*res].n3f[0] = xf;
			verts[x + y*res + res*res].n3f[1] = scale;
			verts[x + y*res + res*res].n3f[2] = -yf;

			verts[x + y*res + 2*res*res].n3f[0] = xf;
			verts[x + y*res + 2*res*res].n3f[1] = -yf;
			verts[x + y*res + 2*res*res].n3f[2] = -scale;

			verts[x + y*res + 3*res*res].n3f[0] = xf;
			verts[x + y*res + 3*res*res].n3f[1] = -scale;
			verts[x + y*res + 3*res*res].n3f[2] = yf;

			verts[x + y*res + 4*res*res].n3f[0] = scale;
			verts[x + y*res + 4*res*res].n3f[1] = xf;
			verts[x + y*res + 4*res*res].n3f[2] = yf;

			verts[x + y*res + 5*res*res].n3f[0] = -scale;
			verts[x + y*res + 5*res*res].n3f[1] = -xf;
			verts[x + y*res + 5*res*res].n3f[2] = yf;
		}
	}
	int numRi = 0;
	for (int x=0; x<numVerts; x++) {
		int ri;
		for (ri = 0; ri<numRi; ri++) {
			if (fabs(verts[x].n3f[0] - verts[ri].n3f[0]) < 0.0001 &&
				fabs(verts[x].n3f[1] - verts[ri].n3f[1]) < 0.0001 &&
				fabs(verts[x].n3f[2] - verts[ri].n3f[2]) < 0.0001)
					break;
		}
		if (ri == numRi) {
			numRi++;
			verts[ri] = verts[x];
			verts[ri].v3f[0] = verts[x].n3f[0] * rf;
			verts[ri].v3f[1] = verts[x].n3f[1] * rf;
			verts[ri].v3f[2] = verts[x].n3f[2] * rf;
		}
		vertIndices[x] = ri;
	}

	int nv = 0;
	for (int x = res-2; x>=0; x--) {
		if (x != res-2) {
			indices[nv++] = vertIndices[x+1 + (res-1)*res + 4*res*res];
			indices[nv++] = vertIndices[x+1 + (res-1)*res + 4*res*res];
		}
		for (int y = res-1; y>=0; y--) {
			indices[nv++] = vertIndices[x+1 + y*res + 4*res*res];
			indices[nv++] = vertIndices[x + y*res + 4*res*res];
		}
		if (x) {
			indices[nv++] = vertIndices[x + res + 4*res*res];
			indices[nv++] = vertIndices[x + res + 4*res*res];
		}
	}

	for (int x = 0; x < res-1; x++) {
		if (x) {
			indices[nv++] = vertIndices[x+1];
			indices[nv++] = vertIndices[x+1];
		}
		for (int y = 0; y < res*4; y++) {
			if (y && vertIndices[x+1 + y*res] == indices[nv-2]) continue;
			indices[nv++] = vertIndices[x + y*res];
			indices[nv++] = vertIndices[x+1 + y*res];
		}

		// Could manage without last two of these if I really tried...
		indices[nv++] = vertIndices[x + (res*4-1)*res];
		indices[nv++] = vertIndices[x + (res*4-1)*res];
	}

	for (int x = res-2; x>=0; x--) {
		indices[nv++] = vertIndices[x+1 + (res-1)*res + 5*res*res];
		indices[nv++] = vertIndices[x+1 + (res-1)*res + 5*res*res];

		for (int y = res-1; y>=0; y--) {
			indices[nv++] = vertIndices[x+1 + y*res + 5*res*res];
			indices[nv++] = vertIndices[x + y*res + 5*res*res];
		}
		if (x) {
			indices[nv++] = vertIndices[x + res + 5*res*res];
			indices[nv++] = vertIndices[x + res + 5*res*res];
		}
	}
	mglMesh *out = CreateMesh(verts, numVerts, indices, nv, GL_QUAD_STRIP, config->drawMode);
	free(verts);
	return out;
}

/*
mglMesh *mglMakeSphereMesh2(double radius) {
	mglNormalVertex vertices[2 + RES*(RES-1)];
	// Too lazy to calculate exactly.
	GLushort indices[3*RES*RES];
	vertices[0].v3f[0] = 0;
	vertices[0].v3f[1] = (float)radius;
	vertices[0].v3f[2] = 0;
	vertices[0].n3f[0] = 0;
	vertices[0].n3f[1] = 1;
	vertices[0].n3f[2] = 0;
	vertices[RES*(RES-1)+1].v3f[0] = 0;
	vertices[RES*(RES-1)+1].v3f[1] = (float)-radius;
	vertices[RES*(RES-1)+1].v3f[2] = 0;
	vertices[RES*(RES-1)+1].n3f[0] = 0;
	vertices[RES*(RES-1)+1].n3f[1] = -1;
	vertices[RES*(RES-1)+1].n3f[2] = 0;
	double incr = 1/(double) RES;
	double radiusSquared = radius*radius;
	for (int h=1; h<RES; h++) {
		double angle = h * M_PI/RES;
		double cosa1 = cos(angle);
		double y = radius*cosa1;
		double r2 = sqrt(1-cosa1*cosa1);
		for (int a = 0; a<RES; a++) {
			int index = 1+(h-1)*RES+a;
			double angle = a * 2 * M_PI/RES;
			vertices[index].n3f[0] = (float) (r2*cos(angle));
			vertices[index].n3f[1] = (float)  cosa1;
			vertices[index].n3f[2] = (float) (r2*sin(angle));

			vertices[index].v3f[0] = (float)(radius*vertices[index].n3f[0]);
			vertices[index].v3f[1] = (float)y;
			vertices[index].v3f[2] = (float)(radius*vertices[index].n3f[2]);
		}
	}

	int index = 0;
	for (int h=0; h<RES; h++) {
		for (int a = 0; a<=RES; a++) {
			if (h<RES-1) {
				indices[index++] = 1+RES*h+(a%RES);
			}
			else {
				indices[index++] = RES*(RES-1)+1;
			}
			if (h) {
				indices[index++] = 1+RES*(h-1)+(a%RES);
			}
			else {
				indices[index++] = 0;
			}
		}
	}
	//glInterleavedArrays(GL_N3F_V3F, 0, vertices);
	//glDrawElements(GL_QUAD_STRIP, index, GL_UNSIGNED_INT, indices);

	return CreateMesh(vertices, 2 + RES*(RES-1), indices, index, GL_QUAD_STRIP, config->drawMode);
}//*/

mglMesh *mglMakeCylinderMesh(double radius, int res) {
	int numVertices = 2*res;
	int numIndices = numVertices+2;
	mglNormalVertex *vertices = (mglNormalVertex*)malloc((numVertices+2) * (sizeof(mglNormalVertex) + sizeof(GLushort)));
	GLushort *indices = (GLushort*) (vertices + numVertices);
	int index = 0;
	double angleIncrement = 2 * M_PI/res;
	for (int a = 0; a<res; a++) {
		double angle = a * angleIncrement;
		vertices[index].n3f[0] = (float)cos(angle);
		vertices[index].n3f[1] = 0.0f;
		vertices[index].n3f[2] = (float)sin(angle);

		vertices[index].v3f[0] = (float)(radius*vertices[index].n3f[0]);
		vertices[index].v3f[1] = 0.0f;
		vertices[index].v3f[2] = (float)(radius*vertices[index].n3f[2]);
		index++;
		vertices[index] = vertices[index-1];
		vertices[index].v3f[1] = 1.0f;
		index++;
	}

	index = 0;
	for (int h=0; h < 2*res; h++) {
		indices[index++] = h;
	}
	indices[index++] = 0;
	indices[index++] = 1;
	mglMesh *out = CreateMesh(vertices, numVertices, indices, numIndices, GL_QUAD_STRIP, config->drawMode);
	free(vertices);
	return out;
}


void mglConvertMatrix(mglMatrix *mgl, const Matrix *m) {
	for (int i=0; i<3; i++) {
		for (int j=0; j<4; j++) {
			mgl->m[i+j*4] = m->M[i][j];
		}
	}
	mgl->m[3] = mgl->m[7] = mgl->m[11] = 0;
	mgl->m[15] = 1;
}

void mglMakeMatrix(Matrix *m, const mglMatrix *mgl);

void mglLoadMatrix(mglMatrix *m) {
	glLoadMatrixf(m->m);
}

void mglLoadMatrix(mglMatrix &m) {
	mglLoadMatrix(&m);
}

void mglMultMatrix(mglMatrix *m) {
	glMultMatrixf(m->m);
}

void mglMultMatrix(mglMatrix &m) {
	mglMultMatrix(&m);
}

void mglMultMatrix(Matrix *m) {
	mglMatrix matrix;
	mglConvertMatrix(&matrix, m);
	mglMultMatrix(matrix);
}
void mglMultMatrix(Matrix &m) {
	mglMultMatrix(&m);
}

void mglLoadMatrix(Matrix *m) {
	mglMatrix matrix;
	mglConvertMatrix(&matrix, m);
	mglLoadMatrix(matrix);
}
void mglLoadMatrix(Matrix &m) {
	mglLoadMatrix(&m);
}

int CheckVBO() {
#ifdef USE_GLEW
	return (glGenBuffers && glBindBuffer && glBufferData && glBufferSubData);
#else
	return 0;
#endif
}

mglMesh *CreateMesh(mglNormalVertex *vertices, int numVertices, GLushort *indices, int numIndices, GLenum glMode, int drawMode) {
	mglMesh *mesh;
	if (drawMode == DRAW_MODE_SLOW || drawMode == DRAW_MODE_INTERLEAVED_ARRAYS) {
		mesh = (mglMesh*) malloc(sizeof(mglMesh) + sizeof(mglNormalVertex) * numVertices + sizeof(GLushort) * numIndices);
		mesh->vertices = (mglNormalVertex*) (mesh+1);
		mesh->indices = (GLushort*) (mesh->vertices+numVertices);

		memcpy(mesh->vertices, vertices, sizeof(mglNormalVertex) * numVertices);

		memcpy(mesh->indices, indices, sizeof(*indices) * numIndices);

		mesh->glId = 0;
		mesh->glIndicesId = 0;
	}
	else {
		mesh = (mglMesh*) calloc(1, sizeof(mglMesh));
	}
	mesh->glMode = glMode;
	mesh->numVertices = numVertices;
	mesh->numIndices = numIndices;

#ifdef USE_GLEW
	if (drawMode == DRAW_MODE_VBO) {
		glGenBuffers(1, &mesh->glId);
		if (!mesh->glId) {
			// Note:  Though display lists are compatible with VBOs,  VBA's are not, except with extra state changes.
			// And I use VBAs to create display lists.
			return CreateMesh(vertices, numVertices, indices, numIndices, glMode, DRAW_MODE_SLOW);
		}
		glBindBuffer(GL_ARRAY_BUFFER, mesh->glId);
		glBufferData(GL_ARRAY_BUFFER, numVertices*sizeof(mglNormalVertex), vertices, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glGenBuffers(1, &mesh->glIndicesId);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->glIndicesId);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*sizeof(*indices), indices, GL_STATIC_DRAW);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}
#endif
	if (drawMode == DRAW_MODE_DISPLAY_LISTS) {
		// TODO:  Possibly don't use interleaved arrays, if not supported.
		mesh->glId = glGenLists(1);
		if (!mesh->glId) {
			free(mesh);
			return CreateMesh(vertices, numVertices, indices, numIndices, glMode, DRAW_MODE_SLOW);
		}
		mesh->vertices = vertices;
		mesh->indices = indices;

		glNewList(mesh->glId, GL_COMPILE);
		mesh->drawMode = DRAW_MODE_INTERLEAVED_ARRAYS;
		// Currently means to ignore color.  May switch to pointers instead at some point.
		Color c = {0,0,0,0};
		mesh->Draw(c);
		glEndList();
	}
	mesh->drawMode = drawMode;

	return mesh;
}

void mglMesh::Draw(Color c) {
	switch(drawMode) {
#ifdef USE_GLEW
	case DRAW_MODE_VBO:
		glBindBuffer(GL_ARRAY_BUFFER, mesh->glId);
		if (this != lastMesh) {
			glVertexPointer(3, GL_FLOAT, sizeof(mglNormalVertex), (char*)(3*sizeof(float)));
			glNormalPointer(GL_FLOAT, sizeof(mglNormalVertex), 0);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glIndicesId);
			lastMesh = this;
		}

		if (!selecting && c.uVal)
			mglColor4(c);
		glDrawElements(glMode, numIndices, GL_UNSIGNED_SHORT, 0);
		break;
#endif
	case DRAW_MODE_INTERLEAVED_ARRAYS:
		if (!selecting && c.uVal)
			mglColor4(c);
		// Slight optimization.  No idea how much it helps.
		if (this != lastMesh) {
			glVertexPointer(3, GL_FLOAT, sizeof(*vertices), vertices->v3f);
			glNormalPointer(GL_FLOAT, sizeof(*vertices), vertices->n3f);
			// A numbed of sources recommend against using interleaved arrays.
			//glInterleavedArrays(GL_N3F_V3F, 0, vertices);
			lastMesh = this;
		}
		glDrawElements(glMode, numIndices, GL_UNSIGNED_SHORT, indices);
		break;
	case DRAW_MODE_DISPLAY_LISTS:
		if (!selecting && c.uVal)
			mglColor4(c);
		glCallList(glId);
		break;
	case DRAW_MODE_SLOW:
	default:
		glBegin(glMode);
		// Shouldn't have to be here, by spec, but some opengl implementations
		// seem to demand it.
		if (!selecting && c.uVal)
			mglColor4(c);
		for (int i=0; i<numIndices; i++) {
			glNormal3fv(vertices[indices[i]].n3f);
			glVertex3fv(vertices[indices[i]].v3f);
		}
		glEnd();
		break;
	}
}

void mglMesh::Destroy() {
	if (drawMode == DRAW_MODE_DISPLAY_LISTS) {
		glDeleteLists(glId, 1);
	}
#ifdef USE_GLEW
	else if (drawMode == DRAW_MODE_VBO) {
		glDeleteBuffers(1, &glId);
		glDeleteBuffers(1, &glIndicesId);
	}
#endif
	free(this);
}

static Color lastColor = {0,0,0,0};

void mglColor3(Color c) {
	lastColor.a = 0xFF;
	if (lastColor.uVal != c.uVal) {
		glColor3ubv(&c.r);
		lastColor = c;
	}
}

void mglColor3(unsigned char r, unsigned char g, unsigned char b) {
	Color c = {r, g, b, 255};
	mglColor3(c);
}

void mglColor4(Color c) {
	if (lastColor.uVal != c.uVal) {
		glColor4ubv(&c.r);
		lastColor = c;
	}
}

void mglColor4(unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
	Color c = {r, g, b, a};
	mglColor4(c);
}


void mglColorInit() {
	Color c = lastColor;
	lastColor.uVal ^= 1;
	mglColor4(c);
}

unsigned int mglMesh::currentDrawMode = 0;
mglMesh *mglMesh::lastMesh = 0;
int mglMesh::selecting = 0;

void mglMesh::Begin(unsigned int &drawMode, int selecting) {
	if (drawMode == DRAW_MODE_VBO) {
		if (!CheckVBO())
			drawMode = DRAW_MODE_DISPLAY_LISTS;
	}
	if (drawMode != DRAW_MODE_SLOW) {
		// Note:  Do this with display lists, too,
		// as it's used to create new display lists.
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
	}
	mglMesh::selecting = selecting;
	lastMesh = 0;
	currentDrawMode = drawMode;
}

void mglMesh::End() {
	if (currentDrawMode != DRAW_MODE_SLOW) {
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
#ifdef USE_GLEW
		if (currentDrawMode == DRAW_MODE_VBO) {
			glBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
		}
#endif
	}
}
